package passme.topcoder.plugin;

import com.topcoder.client.contestant.ProblemComponentModel;

import com.topcoder.shared.language.Language;
import com.topcoder.shared.problem.Constraint;
import com.topcoder.shared.problem.DataType;
import com.topcoder.shared.problem.Renderer;
import com.topcoder.shared.problem.TestCase;

import java.util.HashMap;
import java.util.Map;


/**
 * <p>
 * This tester class is for C++ only. It is modified by TzTester, which is
 * based on PopsProcessor written for Java. It reads in all the given examples
 * for a problem and generates the equivalent C++ code to test all the cases.
 * The accumulated running time is 8 seconds, but it is easy to selectively
 * run a specific case only. This tester will define three tags that can be
 * embedded within PopsEdit/FileEdit code template:
 * </p>
 * 
 * <p>
 * $WRITERCODE$ - place holder for writer code, will be blank if none found.<br>
 * $PROBLEM$ - place holder for problem description as plain text.<br>
 * $RUNTEST$ - place holder for where to put the code that starts the test.<br>
 * $TESTCODE$ - place holder for where to put the test code.
 * </p>
 *
 * @author Yaoting Liu
 */
public class TesterGenerater {
    /** Constants */
    private static final String WRITER_CODE = "$WRITERCODE$";
    private static final String PROBLEM = "$PROBLEM$";
    private static final String RUNTEST = "$RUNTEST$";
    private static final String TESTCODE = "$TESTCODE$";
    private static final String CONSTRAINT = "$CONSTRAINT$";                            
    private static final String VERSION = "\n// Powered by TZTester 1.01 [25-Feb-2003]";

    /** Cut tags */
    private static final String BEGIN_CUT = "\n// BEGIN CUT HERE\n";
    private static final String END_CUT = "\n// END CUT HERE\n";

    /** Map used to store my tags */
    @SuppressWarnings("unchecked")
	private HashMap tags = new HashMap();

    /** Problem-related variables */
    private ProblemComponentModel problem = null;
    private Language language = null;

    /**
     * <p>
     * PreProcess the source code First determines if it is saved code, writer
     * code, or nothing and stores it in $WRITERCODE$ tag Secondly builds a
     * main method with default test cases
     * </p>
     *
     * @param source DOCUMENT ME!
     * @param problem DOCUMENT ME!
     * @param Lang DOCUMENT ME!
     * @param render DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public String preProcess(String source, ProblemComponentModel problem,
        Language Lang, Renderer render) {
        this.problem = problem;
        this.language = Lang;
    	
        // Set defaults for the tags in case we exit out early
        tags.put(WRITER_CODE, "");
        tags.put(PROBLEM, "");
        tags.put(RUNTEST, "");
        tags.put(TESTCODE, "");
        tags.put(CONSTRAINT, "");

        
        // If there is source and the source is NOT equal to the default
        // solution, return it
        if ((source.length() > 0) &&
                !source.equals(problem.getDefaultSolution())) {
            return source;
        }

        // Check to see if the component has any signature
        if (!problem.hasSignature()) {
            tags.put(RUNTEST,
                "// *** WARNING *** Problem has no signature defined for it");
            tags.put(TESTCODE,
                "// *** WARNING *** Problem has no signature defined for it");

            return "";
        }

        // Get the test cases
        TestCase[] TestCases = problem.getTestCases();

        // Check to see if test cases are defined
        if ((TestCases == null) || (TestCases.length == 0)) {
            tags.put(RUNTEST,
                "// *** WARNING *** No test cases defined for this problem");
            tags.put(TESTCODE,
                "// *** WARNING *** No test cases defined for this problem");

            return "";
        }

        // Re-initialize the tags
        tags.clear();
        tags.put(WRITER_CODE, problem.getDefaultSolution());

        try {
            tags.put(PROBLEM, render.toPlainText(language));
        } catch (Exception e) {
        }

        // Generate the code to run test cases
        geneRunTestCode();

        // Generate the test cases.
        geneTestCode();
        
        // Generate the constrains.
        buildConstraints();

        return "";
    }

    private void buildConstraints() {
    	if (!problem.hasConstraints()) {
    		return;
    	}
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < problem.getConstraints().length; ++i) {
            Constraint constraint = problem.getConstraints()[i];
            buffer.append("// ").append(toPaintText(constraint.toXML())).append("\n");            
        }        
        tags.put(CONSTRAINT, buffer.toString());
		// TODO Auto-generated method stub		
	}

	static public String toPaintText(String xmlstr) {
		String text = xmlstr.replaceAll("<[^>]*>", "");
		text = text.replaceAll("&amp;lt;", "<");
		text = text.replaceAll("&amp;gt;", ">");
		text = text.replaceAll("&nbsp;", " ");
		return text;
	}

	/**
     * DOCUMENT ME!
     *
     * @param source
     * @param language
     *
     * @return
     */
    public String postProcess(String source, Language language) {
        StringBuffer Buffer = new StringBuffer(source);

        // Insert a version string
        Buffer.append(VERSION);

        return Buffer.toString();
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public Map getUserDefinedTags() {
        return tags;
    }

    private void geneRunTestCode() {
        StringBuffer code = new StringBuffer();

        code.append(BEGIN_CUT);

        // Use a static variable to avoid infinite recursion
        code.append("\tstatic bool s_FirstTime = true;\n");
        code.append("\tif (s_FirstTime) {\n");
        code.append("\t\ts_FirstTime = false;\n");
        code.append("\t\tTest();\n");
        code.append("\t}");

        code.append(END_CUT);

        tags.put(RUNTEST, code.toString());
    }

    /**
     * This method will generate the code for the test cases.
     */
    private void geneTestCode() {
        DataType[] paramTypes = problem.getParamTypes();
        DataType returnType = problem.getReturnType();
        TestCase[] testCases = problem.getTestCases();
        StringBuffer testCodes = new StringBuffer();

        testCodes.append("public:\n");

        // Generate the test wrapper function that can call either all
        // or individual test cases. (-1 for all)
        testCodes.append("void Test(int casei = -1) {\n");

        // Check which case should be called
        for (int i = 0; i < testCases.length; ++i)
            testCodes.append("\tif ((casei == -1) || (casei == " + i +
                ")) test" + i + "();\n");

        testCodes.append("}\n");

        testCodes.append("private:\n");

        // Generate the vector output function
        testCodes.append(
            "template <typename T> string print_array(const vector<T> &V) {\n");
        testCodes.append("\tostringstream os;\n");
        testCodes.append("\tos << \"{ \";\n");
        testCodes.append(
            "\tfor (typename vector<T>::const_iterator iter = V.begin(); iter != V.end(); ++iter) {\n");
        testCodes.append("\t\tos << \'\\\"\' << *iter << \"\\\",\";\n");
        testCodes.append("\t}\n");
        testCodes.append("\tos << \" }\";\n");
        testCodes.append("\treturn os.str();\n");
        testCodes.append("}\n");

        // Generate the verification function
        generateVerificationCode(testCodes, returnType);

        // Generate the individual test cases
        for (int i = 0; i < testCases.length; ++i) {
            buildTestCase(i, testCodes, paramTypes, returnType, testCases[i]);
        }

        // Insert the cut tags
        testCodes.insert(0, BEGIN_CUT);
        testCodes.append(END_CUT);

        tags.put(TESTCODE, testCodes.toString());
    }

    /**
     * DOCUMENT ME!
     *
     * @param code
     * @param returnType
     */
    private void generateVerificationCode(StringBuffer code, DataType returnType) {
        String TypeString = returnType.getDescriptor(language);

        code.append("void verify_case(int casei, const ").append(TypeString)
            .append(" &expected, const ").append(TypeString)
            .append(" &received) {\n");

        if (returnType.getDimension() == 0) {
            code.append(
                "\tcerr << \"Expected: \\\"\" << expected << \'\\\"\' << endl;\n");
            code.append(
                "\tcerr << \"Received: \\\"\" << received << \'\\\"\' << endl;\n");
        } else {
            code.append(
                "\tcerr << \"Expected: \" << print_array(expected) << endl;\n");
            code.append(
                "\tcerr << \"Received: \" << print_array(received) << endl;\n");
        }
        // Print "PASSED" or "FAILED" based on the result
        code.append("\tif (expected == received)\n");
        code.append("\t\tcerr << \">>PASSED!\" << endl;\n");
        code.append("\telse {\n");
        code.append(
            "\t\tcerr << \">>FAILED~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\" << endl;\n");        
        code.append("\t}\n");
        code.append("\tcerr << endl;\n");
        code.append("}\n");
    }

    /**
     * DOCUMENT ME!
     *
     * @param index
     * @param code
     * @param paramTypes
     * @param returnType
     * @param testCase
     */
    private void buildTestCase(int index, StringBuffer code,
        DataType[] paramTypes, DataType returnType, TestCase testCase) {
        String[] inputs = testCase.getInput();
        String outputs = testCase.getOutput();
        String desc = returnType.getDescription();

        // Generate code for setting up individual test cases and calling the
        // method with these parameters.
        code.append("void test" + index + "() {\n");

        // Generate each input variable separately
        for (int i = 0; i < inputs.length; ++i) {
            buildVariable(i, code, paramTypes[i], inputs[i]);            
        }

        // Generate the output variable as the last variable
        buildVariable(inputs.length, code, returnType, outputs);
        
        // Print param info.
        code.append("\tcerr << \"Test Case #").append(index).append("...\" << endl;\n");
        for (int i = 0; i < inputs.length; ++i) {
        	code.append("\tcerr << ").append("\""+ this.problem.getParamNames()[i] + ": ");
        	if (paramTypes[i].getDimension() != 0) {
        		code.append("\\n");
        	}
        	code.append("\\\"\" << ").append(inputs[i]).append(" << \"\\\"\" << endl;\n");
        }
        
        //printTestInfo(index, inputs, outputs);
        code.append("\tverify_case(").append(index).append(", arg")
            .append(inputs.length).append(", ").append(problem.getMethodName())
            .append("(");

        // Generate the function call list
        for (int i = 0; i < inputs.length; ++i) {
            code.append("arg" + i);

            if (i < (inputs.length - 1)) {
                code.append(", ");
            }
        }

        code.append("));\n");        
        code.append("}\n");
    }

    /**
     * DOCUMENT ME!
     *
     * @param index
     * @param code
     * @param paramType
     * @param input
     */
    private void buildVariable(int index, StringBuffer code,
        DataType paramType, String input) {
        String typeName = paramType.getBaseName();

        if (paramType.getDimension() == 0) {
            // Just a scalar value, simply initialize it at declaration (long
            // integers need an 'L' tagged on)
            if (typeName.equals("long") || typeName.equals("Long")) {
                code.append("\t").append(paramType.getDescriptor(language))
                    .append(" arg").append(index).append(" = ").append(input)
                    .append("LL;\n");
            } else {
                code.append("\t").append(paramType.getDescriptor(language))
                    .append(" arg").append(index).append(" = ").append(input)
                    .append(";\n");
            }
        } else {
            // Arrays need to be converted to vector<type> before passing
            code.append("\t").append(typeName.toLowerCase())
                .append(" arr").append(index).append("[] = \n").append(input)
                .append(";\n");
            code.append("\t").append(paramType.getDescriptor(language))
                .append(" arg").append(index).append("(arr").append(index)
                .append(", arr").append(index + " + (sizeof(arr")
                .append(index).append(") / sizeof(arr").append(index)
                .append("[0])));\n");
        }
    }
}
